﻿using System;
using System.Collections.Generic;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.Diagnostics.Management;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WADConfigureWebRole
{
	/// <summary>
	/// This class allow you to setup performance counters in a custom manner. If you simply call 
	/// ConfigureAzureDiagnostics() it will take its built-in defaults for counter configuration.  
	/// You may instance the class and set the class properties to alter the defaults before calling
	/// ConfigureAzureDiagnostics().
	/// To fine-tune the counters you must modify the parameters in each of the counters in the 
	/// GetPerformanceCounterItems() method.
	/// </summary>
	/// <remarks>
	/// Initialization Issues with Diagnostics and Instance Scaling
	/// 
	/// Why is this not automatic and why is it not in the worker role (InstanceController)?
	/// It is not automatic because the owner must enter configuration from his/her Azure Portal for the 
	/// system to be able to manage instances.
	/// 
	/// It is not in the worker role because the worker is not responsible for the monitored (web role) 
	/// application's configuration and it has no knowledge of the performance counters that are of
	/// interest to the web role (monitored) application.
	/// 
	/// There are two parts to Diagnostics and Scaling
	/// 1 - Azure Collection of Data Points
	/// 2 - Worker Role processing of collected data points
	/// 
	/// 1-
	/// Azure performs the actual collection of performance and event data.  The developer is tasked with
	/// deciding which data points are "of interest" and configuring the collection of those int table storage.
	/// Azure creates and maintains the tables to collect the data points.
	/// 
	/// Initialization of diagnostics should occur normally when the application starts.  However it is now
	/// possible to re-configure diagnostics at anytime.  In this application it is done here in the OnStart().
	/// 
	/// 2-
	/// The worker role relies on Azure and the web role(s) to create the data points of interest.  Since the 
	/// web roles may vary dramatically and contain information that is specific to the web role it must create
	/// its configuration that the worker role (Instance Monitor) will consume.
	/// 
	/// Initialization can be performed in a number of ways. In this case a web page has the inputs for users to
	/// enter information obtained from the Azure Portal and information contained in the web.config files that
	/// are decided by the development team such as web role names and sampling configuration settings.
	/// 
	/// These are entered either directly into the application settings or into the Global.ascx.cs performance
	/// counters class.
	/// 
	/// The application cannot scale without these settings.	
	/// </remarks>
	public class PlugIn
	{
		#region Default settings if not specified by outside code

		private static string _wadConnectionString = "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";

		private static int _directoriesBufferQuotaInMB = 512;

		private static int _diagnosticInfrastructureLogsBufferQuotaInMB = 10;

		private static int _logsBufferQuotaInMB = 10;

		private static int _windowsEventLogBufferQuotaInMB = 10;

		private static int _performanceCountersBufferQuotaInMB = 30;

		private static double _directoriesScheduledTransferPeriod = 1.0d;

		private static double _diagnosticInfrastructureLogsScheduledTransferPeriod = 1.0d;

		private static double _logsScheduledTransferPeriod = 1.0d;

		private static double _windowsEventLogScheduledTransferPeriod = 1.0d;

		private static double _performanceCountersScheduledTransferPeriod = 1.0d;

		#endregion

		#region Properties of PlugIn

		/// <summary>
		/// Gets or sets the WAD connection string.
		/// </summary>
		/// <value>The WAD connection string.</value>
		public static string WADConnectionString
		{
			get { return _wadConnectionString; }
			set { _wadConnectionString = value; }
		}

		/// <summary>
		/// Gets or sets the directories buffer quota in MB.
		/// </summary>
		/// <value>The directories buffer quota in MB.</value>
		public static int DirectoriesBufferQuotaInMB
		{
			get { return _directoriesBufferQuotaInMB; }
			set { _directoriesBufferQuotaInMB = value; }
		}

		/// <summary>
		/// Gets or sets the diagnostic infrastructure logs buffer quota in MB.
		/// </summary>
		/// <value>The diagnostic infrastructure logs buffer quota in MB.</value>
		public static int DiagnosticInfrastructureLogsBufferQuotaInMB
		{
			get { return _diagnosticInfrastructureLogsBufferQuotaInMB; }
			set { _diagnosticInfrastructureLogsBufferQuotaInMB = value; }
		}

		/// <summary>
		/// Gets or sets the logs buffer quota in MB.
		/// </summary>
		/// <value>The logs buffer quota in MB.</value>
		public static int LogsBufferQuotaInMB
		{
			get { return _logsBufferQuotaInMB; }
			set { _logsBufferQuotaInMB = value; }
		}

		/// <summary>
		/// Gets or sets the windows event log buffer quota in MB.
		/// </summary>
		/// <value>The windows event log buffer quota in MB.</value>
		public static int WindowsEventLogBufferQuotaInMB
		{
			get { return _windowsEventLogBufferQuotaInMB; }
			set { _windowsEventLogBufferQuotaInMB = value; }
		}

		/// <summary>
		/// Gets or sets the performance counters buffer quota in MB.
		/// </summary>
		/// <value>The performance counters buffer quota in MB.</value>
		public static int PerformanceCountersBufferQuotaInMB
		{
			get { return _performanceCountersBufferQuotaInMB; }
			set { _performanceCountersBufferQuotaInMB = value; }
		}

		/// <summary>
		/// Gets or sets the directories scheduled transfer period.
		/// </summary>
		/// <value>The directories scheduled transfer period.</value>
		public static double DirectoriesScheduledTransferPeriod
		{
			get { return _directoriesScheduledTransferPeriod; }
			set { _directoriesScheduledTransferPeriod = value; }
		}

		/// <summary>
		/// Gets or sets the diagnostic infrastructure logs scheduled transfer period.
		/// </summary>
		/// <value>The diagnostic infrastructure logs scheduled transfer period.</value>
		public static double DiagnosticInfrastructureLogsScheduledTransferPeriod
		{
			get { return _diagnosticInfrastructureLogsScheduledTransferPeriod; }
			set { _diagnosticInfrastructureLogsScheduledTransferPeriod = value; }
		}

		/// <summary>
		/// Gets or sets the logs scheduled transfer period.
		/// </summary>
		/// <value>The logs scheduled transfer period.</value>
		public static double LogsScheduledTransferPeriod
		{
			get { return _logsScheduledTransferPeriod; }
			set { _logsScheduledTransferPeriod = value; }
		}

		/// <summary>
		/// Gets or sets the windows event log scheduled transfer period.
		/// </summary>
		/// <value>The windows event log scheduled transfer period.</value>
		public static double WindowsEventLogScheduledTransferPeriod
		{
			get { return _windowsEventLogScheduledTransferPeriod; }
			set { _windowsEventLogScheduledTransferPeriod = value; }
		}

		/// <summary>
		/// Gets or sets the performance counters scheduled transfer period.
		/// </summary>
		/// <value>The performance counters scheduled transfer period.</value>
		public static double PerformanceCountersScheduledTransferPeriod
		{
			get { return _performanceCountersScheduledTransferPeriod; }
			set { _performanceCountersScheduledTransferPeriod = value; }
		}

		#endregion

		#region Performance Counters Configuration Specifics

		#region Counter string constants

		public const string ProcessorCounterName = @"\Processor(_Total)\% Processor Time";
		public const string ProcessorFriendlyName = "PercentProcessorTime";

		public const string ApplicationsTotalRequestsPerSecName = @"\ASP.NET Applications(__Total__)\Requests/Sec";
		public const string ApplicationsTotalRequestsPerSecFriendlyName = "ApplicationsTotalRequestsPerSec";

		public const string RequestsRejectedName = @"\ASP.NET\Requests Rejected";
		public const string RequestsRejectedFriendlyName = "RequestsRejected";

		public const string RequestWaitTimeName = @"\ASP.NET\Request Wait Time";
		public const string RequestWaitTimeFriendlyName = "RequestWaitTime";

		public const string MemoryAvailableMbytesName = @"\Memory\Available Mbytes";
		public const string MemoryAvailableMbytesFriendlyName = "MemoryAvailableMbytes";

		public const string ConnectionsEstablishedName = @"\TCPv4\Connections Established";
		public const string ConnectionsEstablishedFriendlyName = "ConnectionsEstablished";

		public const string NetworkBytesReceivedPerSecondName = @"\Network Interface(*)\Bytes Received/sec";
		public const string NetworkBytesReceivedPerSecondFriendlyName = "NetworkBytesReceivedPerSecond";

		public const string NetworkBytesSentPerSecondName = @"\Network Interface(*)\Bytes Sent/sec";
		public const string NetworkBytesSentPerSecondFriendlyName = "NetworkBytesSentPerSecond";

		#endregion

		/// <summary>
		/// Adds the performance counters. 
		/// The order of precedence is set by priority parameter value (lower value is higher priority). 
		/// For example: 1 has a higher priority than 10 since it appears earlier on a number line.
		/// </summary>
		/// <param name="config">The config.</param>
		private static void AddPerformanceCounters(DiagnosticMonitorConfiguration config)
		{
			var counterItems = GetPerformanceCounterItems();
			foreach (var performanceCounterItem in counterItems)
			{
				if (performanceCounterItem.PollingInterval >= 0)	//values less than 0 disables the counter
				{
					config.PerformanceCounters.DataSources.Add(new PerformanceCounterConfiguration
					                                           	{
					                                           		CounterSpecifier = performanceCounterItem.CounterName,
					                                           		SampleRate = TimeSpan.FromSeconds(performanceCounterItem.SampleRate)
					                                           	});
				}
			}
		}

		/// <summary>
		/// Gets the performance counter items.
		/// Microsoft recommended Performance Monitoring http://msdn.microsoft.com/en-us/library/fxk122b4.aspx
		/// The value associated with each performance counter is updated every 400 milliseconds.
		/// </summary>
		/// <returns>
		/// List<see cref="PerformanceCounterItem"/>
		/// </returns>
		/// <remarks>These performance counters should help to regulate the number of Azure instances and to
		/// detect out of bounds conditions that might be due to code issues or DoS attachs.</remarks>
		public static List<PerformanceCounterItem> GetPerformanceCounterItems()
		{
			var performanceCounterItems = new List<PerformanceCounterItem>();

			// Adding or deleting counters.
			// If you decide to delete a counter that has been used in the past DO NOT 
			// remove it from this list. Instead set its PollingInterval to -1. When 
			// you subsequently run the initialization process the initializer will 
			// remove the now unused counter.

			//The percentage of time the threads of this process spend using the processors.
			var p1 = new PerformanceCounterItem		//Threshold: 70%. Values greater than this for extended periods of time indicate a need to scale instances.
			{
				CounterName = ProcessorCounterName,
				SampleRate = 10d,
				// following define the values for the Instance Monitor
				FriendlyName = ProcessorFriendlyName,
				IncThreshold = 70,			// a percent
				DecThreshold = 50,			// a percent
				PaddingSeconds = 300,		// seconds
				PriorityLevel = 100,		// set the priority or order of evaluation
				PollingInterval = 600		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p1);

			// Suggests the amount of traffic hitting the site.
			var p6 = new PerformanceCounterItem //
			{
				CounterName = ApplicationsTotalRequestsPerSecName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = ApplicationsTotalRequestsPerSecFriendlyName,
				IncThreshold = 5,			// uses a multiplier to define the value 
				DecThreshold = 1,			// uses a multiplier to define the value 
				PaddingSeconds = 300,		// seconds
				PriorityLevel = 90,			// set the priority or order of evaluation
				PollingInterval = 600		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p6);

			//Requests can be rejected for a number of reasons. Backend latency, such as that caused by a slow SQL server, 
			//is often preceded by a sudden increase in the number of pipeline instances and a decrease in CPU utilization 
			//and Requests/sec. A server may be overwhelmed during times of heavy load due to processor or memory constraints 
			//that ultimately result in the rejection of requests. 
			var p2 = new PerformanceCounterItem

			{
				CounterName = RequestsRejectedName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = RequestsRejectedFriendlyName,
				IncThreshold = 0,			// The value of this counter should be 0. Values greater than this should be investigated. 
				DecThreshold = 5,
				PaddingSeconds = 300,		// seconds
				PriorityLevel = 80,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p2);

			//The number of milliseconds that the most recent request spent waiting in the queue, or named pipe, that 
			//exists between inetinfo and aspnet_wp (see description of Requests Queued). This does not include any 
			//time spent waiting in the application queues. 
			var p3 = new PerformanceCounterItem
			{
				CounterName = RequestWaitTimeName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = RequestWaitTimeFriendlyName,
				IncThreshold = 1000,		// The average request should spend 0 milliseconds waiting in the queue. 
				DecThreshold = 500,
				PaddingSeconds = 300,		// seconds
				PriorityLevel = 70,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p3);

			var p4 = new PerformanceCounterItem // 
			{
				CounterName = MemoryAvailableMbytesName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = MemoryAvailableMbytesFriendlyName,
				IncThreshold = -1,			// don't care 
				DecThreshold = -1,
				PaddingSeconds = -1,
				PriorityLevel = 50,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p4);

			var p5 = new PerformanceCounterItem // 
			{
				CounterName = ConnectionsEstablishedName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = ConnectionsEstablishedFriendlyName,
				IncThreshold = -1,
				DecThreshold = -1,
				PaddingSeconds = -1,
				PriorityLevel = 50,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p5);

			var p7 = new PerformanceCounterItem //
			{
				CounterName = NetworkBytesReceivedPerSecondName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = NetworkBytesReceivedPerSecondFriendlyName,
				IncThreshold = -1,		// don't care 
				DecThreshold = -1,
				PaddingSeconds = -1,
				PriorityLevel = 50,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p7);

			var p8 = new PerformanceCounterItem // 
			{
				CounterName = NetworkBytesSentPerSecondName,
				SampleRate = 20d,
				// following define the values for the Instance Monitor
				FriendlyName = NetworkBytesSentPerSecondFriendlyName,
				IncThreshold = -1,		// don't care 
				DecThreshold = -1,
				PaddingSeconds = -1,
				PriorityLevel = 50,			// set the priority or order of evaluation
				PollingInterval = -1		// seconds setting to -1 tells initializer to skip this metric
			};
			performanceCounterItems.Add(p8);

			return performanceCounterItems;
		}

		#endregion

		/// <summary>
		/// This class defines a performance counter item.  It has both the
		/// WAD required properties and the Instance Monitor properties.
		/// </summary>
		public static void ConfigureAzureDiagnostics()
		{
			// Corresponds to the name in the XML in the ServiceConfiguration file
			var cloudStorageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue(_wadConnectionString));

			// Get a role instance diagnostic manager object
			var roleInstanceDiagnosticManager = cloudStorageAccount.CreateRoleInstanceDiagnosticManager
				(
					RoleEnvironment.DeploymentId,
					RoleEnvironment.CurrentRoleInstance.Role.Name,
					RoleEnvironment.CurrentRoleInstance.Id);

			//var config = DiagnosticMonitor.GetDefaultInitialConfiguration();									// 1.2 version, get the current config
			var diagnosticMonitorConfiguration = roleInstanceDiagnosticManager.GetCurrentConfiguration();		// 1.3 version, get the current config
			diagnosticMonitorConfiguration.ConfigurationChangePollInterval = TimeSpan.FromSeconds(60.0);

			//transfer the IIS and IIS Failed Request Logs
			diagnosticMonitorConfiguration.Directories.BufferQuotaInMB = _directoriesBufferQuotaInMB;
			diagnosticMonitorConfiguration.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(_directoriesScheduledTransferPeriod);	// set to longer interval on deployed version (5 min)

			diagnosticMonitorConfiguration.DiagnosticInfrastructureLogs.BufferQuotaInMB = _diagnosticInfrastructureLogsBufferQuotaInMB;
			diagnosticMonitorConfiguration.DiagnosticInfrastructureLogs.ScheduledTransferPeriod = TimeSpan.FromMinutes(_diagnosticInfrastructureLogsScheduledTransferPeriod);
			diagnosticMonitorConfiguration.DiagnosticInfrastructureLogs.ScheduledTransferLogLevelFilter = LogLevel.Error;

			diagnosticMonitorConfiguration.Logs.BufferQuotaInMB = _logsBufferQuotaInMB;
			diagnosticMonitorConfiguration.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(_logsScheduledTransferPeriod);
			diagnosticMonitorConfiguration.Logs.ScheduledTransferLogLevelFilter = LogLevel.Error;


			diagnosticMonitorConfiguration.WindowsEventLog.BufferQuotaInMB = _windowsEventLogBufferQuotaInMB;
			diagnosticMonitorConfiguration.WindowsEventLog.DataSources.Add("Application!*");
			diagnosticMonitorConfiguration.WindowsEventLog.DataSources.Add("System!*");
			diagnosticMonitorConfiguration.WindowsEventLog.ScheduledTransferPeriod = TimeSpan.FromMinutes(_windowsEventLogScheduledTransferPeriod);
			diagnosticMonitorConfiguration.WindowsEventLog.ScheduledTransferLogLevelFilter = LogLevel.Information;

			// Add the performance counters desired
			diagnosticMonitorConfiguration.PerformanceCounters.BufferQuotaInMB = _performanceCountersBufferQuotaInMB;
			AddPerformanceCounters(diagnosticMonitorConfiguration);
			diagnosticMonitorConfiguration.PerformanceCounters.ScheduledTransferPeriod = TimeSpan.FromMinutes(_performanceCountersScheduledTransferPeriod);

			// Commit the changes
			roleInstanceDiagnosticManager.SetCurrentConfiguration(diagnosticMonitorConfiguration);
		
		}

	}
}

/*
Initialization Issues with Diagnostics and Instance Scaling
 
Why is this not automatic and why is it not in the worker role (InstanceController)?
It is not automatic because the owner must enter configuration from his/her Azure Portal for the 
system to be able to manage instances.

It is not in the worker role because the worker is not responsible for the monitored (web role) 
application's configuration and it has no knowledge of the performance counters that are of
interest to the web role (monitored) application.

There are two parts to Diagnostics and Scaling
1 - Azure Collection of Data Points
2 - Worker Role processing of collected data points

1-
Azure performs the actual collection of performance and event data.  The developer is tasked with
deciding which data points are "of interest" and configuring the collection of those int table storage.
Azure creates and maintains the tables to collect the data points.

Initialization of diagnostics should occur normally when the application starts.  However it is now
possible to re-configure diagnostics at anytime.  In this application it is done here in the OnStart().

2-
The worker role relies on Azure and the web role(s) to create the data points of interest.  Since the 
web roles may vary dramatically and contain information that is specific to the web role it must create
its configuration that the worker role (Instance Monitor) will consume.

Initialization can be performed in a number of ways. In this case a web page has the inputs for users to
enter information obtained from the Azure Portal and information contained in the web.config files that
are decided by the development team such as web role names and sampling configuration settings.

These are entered either directly into the application settings or into the Global.ascx.cs performance
counters class.

The application cannot scale without these settings.

*/